package java核心及工具类;

//public class cloneable接口 {
//
//}
//《cloneable接口的作用》
//cloneable其实就是一个标记接口，只有实现这个接口后，然后在类中重写Object中的clone方法，然后通过类调用clone方法才能克隆成功，如果不实现这个接口，
//则会抛出CloneNotSupportedException(克隆不被支持)异常。Object中clone方法：
//
//protected native Object Clone() throws CloneNotSupportedException;
//
//这里有一个疑问，Object中的clone方法是一个空的方法，那么他是如何判断类是否实现了cloneable接口呢？
//
//原因在于这个方法中有一个native关键字修饰。
//
//   native修饰的方法都是空的方法，但是这些方法都是有实现体的（这里也就间接说明了native关键字不能与abstract同时使用。因为abstract修饰的方法与java的
//接口中的方法类似，他显式的说明了修饰的方法，在当前是没有实现体的，abstract的方法的实现体都由子类重写），只不过native方法调用的实现体，都是非java代码编写
//的（例如：调用的是在jvm中编写的C的接口），每一个native方法在jvm中都有一个同名的实现体，native方法在逻辑上的判断都是由实现体实现的，另外这种native修饰
//的方法对返回类型，异常控制等都没有约束。 
//
//   由此可见，这里判断是否实现cloneable接口，是在调用jvm中的实现体时进行判断的。
//
//《深入理解深度克隆与浅度克隆》
//首先，在java中创建对象的方式有四种：
//
//   一种是new，通过new关键字在堆中为对象开辟空间，在执行new时，首先会看所要创建的对象的类型，知道了类型，才能知道需要给这个对象分配多大的内存区域，分配内存
//后，调用对象的构造函数，填充对象中各个变量的值，将对象初始化，然后通过构造方法返回对象的地址；
//
//   另一种是clone，clone也是首先分配内存，这里分配的内存与调用clone方法对象的内存相同，然后将源对象中各个变量的值，填充到新的对象中，填充完成后，clone
//方法返回一个新的地址，这个新地址的对象与源对象相同，只是地址不同。
//
//另外还有输入输出流，反射构造对象等
//
//下面通过几个例子来解析下浅度克隆与深度克隆的区别：
//
//《浅度克隆测试：》
//
//首先定义一个学生类

//class Student {
//	private String name; // 姓名
//	private int age; // 年龄
//	private StringBuffer sex; // 性别
//
//	public String getName() {
//		return name;
//	}
//
//	public void setName(String name) {
//		this.name = name;
//	}
//
//	public int getAge() {
//		return age;
//	}
//
//	public void setAge(int age) {
//		this.age = age;
//	}
//
//	public StringBuffer getSex() {
//		return sex;
//	}
//
//	public void setSex(StringBuffer sex) {
//		this.sex = sex;
//	}
//
//	@Override
//	public String toString() {
//		return "Student [name=" + name + ", age=" + age + ", sex=" + sex + "]";
//	}
//
//}
////其次定义一个学校类，类中重写clone方法
//
//class School implements Cloneable {
//	private String schoolName; // 学校名称
//	private int stuNums; // 学校人数
//	private Student stu; // 一个学生
//
//	public String getSchoolName() {
//		return schoolName;
//	}
//
//	public void setSchoolName(String schoolName) {
//		this.schoolName = schoolName;
//	}
//
//	public int getStuNums() {
//		return stuNums;
//	}
//
//	public void setStuNums(int stuNums) {
//		this.stuNums = stuNums;
//	}
//
//	public Student getStu() {
//		return stu;
//	}
//
//	public void setStu(Student stu) {
//		this.stu = stu;
//	}
//
//	@Override
//	protected School clone() throws CloneNotSupportedException {
//		// TODO Auto-generated method stub
//		return (School) super.clone();
//	}
//
//	@Override
//	public String toString() {
//		return "School [schoolName=" + schoolName + ", stuNums=" + stuNums + ", stu=" + stu + "]";
//	}
//
//}
//
////最后定义一个main类来测试一下：
//public class cloneable接口 {
//	public static void main(String[] args) throws CloneNotSupportedException {
//		School s1 = new School();
//		s1.setSchoolName("实验小学");
//		s1.setStuNums(100);
//		Student stu1 = new Student();
//		stu1.setAge(20);
//		stu1.setName("zhangsan");
//		stu1.setSex(new StringBuffer("男"));
//		s1.setStu(stu1);
//		System.out.println("s1: " + s1 + " s1的hashcode:" + s1.hashCode() + "  s1中stu1的hashcode:" + s1.getStu().hashCode());
//		School s2 = s1.clone(); // 调用重写的clone方法，clone出一个新的school---s2
//		System.out.println("s2: " + s2 + " s2的hashcode:" + s2.hashCode() + " s2中stu1的hashcode:" + s2.getStu().hashCode());
//
//		s2.setSchoolName("aaaa");//修改s2的String类型数据，s1的相应数据不会变化
//		System.out.println("s1: " + s1 + " s1的hashcode:" + s1.hashCode() + "  s1中stu1的hashcode:" + s1.getStu().hashCode());
//		System.out.println("s2: " + s2 + " s2的hashcode:" + s2.hashCode() + " s2中stu1的hashcode:" + s2.getStu().hashCode());		
//
////		Student stu2=s2.getStu();
////		stu2.setAge(10);//修改s2的Student类型数据,s1的相应数据发生变化
////		System.out.println("s1: " + s1 + " s1的hashcode:" + s1.hashCode() + "  s1中stu1的hashcode:" + s1.getStu().hashCode());
////		System.out.println("s2: " + s2 + " s2的hashcode:" + s2.hashCode() + " s2中stu1的hashcode:" + s2.getStu().hashCode());			
//		
//	}
//}
//测试结果：
//s1: School [schoolName=实验小学, stuNums=100, stu=Student [name=zhangsan, age=20, sex=男]] s1的hashcode:509886383  s1中stu1的hashcode:1854778591
//s2: School [schoolName=实验小学, stuNums=100, stu=Student [name=zhangsan, age=20, sex=男]] s2的hashcode:2054798982 s2中stu1的hashcode:1854778591
//
//   可以看出s1与s2的hashcode不同，也就是说clone方法并不是把s1的引用赋予s2，而是在堆中重新开辟了一块空间，将s1复制过去，将新的地址返回给s2。
//
//   但是s1中stu的hashcode与s2中stu的hashcode相同，也就是这两个指向了同一个对象，修改s2中的stu会造成s1中stu数据的改变。但是修改s2中的基
//本数据类型与Stirng类型时，不会造成s1中数据的改变，基本数据类型例如int，在clone的时候会重新开辟一个四个字节的大小的空间，将其赋值。而String则由
//于String变量的唯一性，如果在s2中改变了String类型的值，则会生成一个新的String对象，对之前的没有影响。这就是浅度克隆。
//
//如何实现深度clone？(下面时第一种方法，另外使用序列化将student变成流，输入再输出也可以)
//
//首先需要让student重写clone方法，实现cloneable接口
//
//public class Student implements Cloneable{
//	
//	private String name;
//	private int age;
//	private StringBuffer sex;
//	public String getName() {
//		return name;
//	}
//	public void setName(String name) {
//		this.name = name;
//	}
//	public int getAge() {
//		return age;
//	}
//	public void setAge(int age) {
//		this.age = age;
//	}
//	public StringBuffer getSex() {
//		return sex;
//	}
//	public void setSex(StringBuffer sex) {
//		this.sex = sex;
//	}
//	@Override
//	public String toString() {
//		return "Student [name=" + name + ", age=" + age + ", sex=" + sex + "]";
//	}
//	@Override
//	protected Student clone() throws CloneNotSupportedException {
//		// TODO Auto-generated method stub
//		return (Student)super.clone();
//	}
//}
//然后，在school的clone方法中将school中的stu对象手动clone一下。
//
//	@Override
//	protected School clone() throws CloneNotSupportedException {
//		// TODO Auto-generated method stub
//		School s = null;
//		s = (School)super.clone();
//		s.stu = stu.clone();
//		return s;
//	}
//再次执行main方法查看结果：
//
//public class Main {
//public static void main(String[] args) throws CloneNotSupportedException {
//	School s1 = new School();       
//	s1.setSchoolName("实验小学");
//	s1.setStuNums(100);
//	Student stu1 = new Student();
//	stu1.setAge(20);
//	stu1.setName("zhangsan");
//	stu1.setSex(new StringBuffer("男"));
//	s1.setStu(stu1);
//	System.out.println("s1: "+s1+" s1的hashcode:"+s1.hashCode()+"  s1中stu1的hashcode:"+s1.getStu().hashCode());
//	School s2 = s1.clone();  //调用重写的clone方法，clone出一个新的school---s2
//	System.out.println("s2: "+s2+" s2的hashcode:"+s2.hashCode()+" s2中stu1的hashcode:"+s2.getStu().hashCode());
//	
//	//修改s2中的值,看看是否会对s1中的值造成影响
//		 s2.setSchoolName("希望小学");
//		 s2.setStuNums(200);
//		 Student stu2 = s2.getStu();
//		 stu2.setAge(30);
//		 stu2.setName("lisi");
//		 stu2.setSex(stu2.getSex().append("6666666"));
//		 s2.setStu(stu2);
//		 
//		 //再次打印两个school，查看结果
//		 System.out.println("-------------------------------------------------------------------------");
//		 System.out.println("s1: "+s1+" hashcode:"+s1.hashCode()+"  s1中stu1的hashcode:"+s1.getStu().hashCode());
//		 System.out.println("s2: "+s2+" hashcode:"+s2.hashCode()+" s2中stu1的hashcode:"+s2.getStu().hashCode());
//}
//}
//打印结果：
//
//
//
//这里可以看到两个stu的hashcode已经不同了，说明这已经是两个对象了，但是在s2中修改sex的值，为什么还会影响到s1呢？
//
//  原因在于sex的类型是Stringbuffer，在clone的时候将StringBuffer对象的地址传递了过去，而StringBuffer类型没有实现cloneable接口，也没有重写clone方法。
//
//这种情况应该怎么解决呢？
//
//1.只实现浅度clone
//
//2.stu2.setSex(new StringBuffer("newString"));  在设置stu2的sex时创建一个新的StringBuffer对象。

//在 Java 开发中，对象拷贝或者说对象克隆是常有的事，对象克隆最终都离不开直接赋值、浅拷贝、深拷贝 这三种方式，其中直接赋值应该是我们最常用的一种方式吧，
//对于浅拷贝和深拷贝可能用的少，所以或多或少存在一些误区，这篇文章会详细的介绍这三种对象克隆方式。
//
//前置知识
//值类型：Java 的基本数据类型，例如 int、float 
//引用类型：自定义类和 Java 包装类（string、integer）

//《01 直接赋值》
//直接赋值是我们最常用的方式，在我们代码中的体现是Persona = new Person();Person b = a，是一种简单明了的方式，但是它只是拷贝了对象引用地址而已，
//并没有在内存中生成新的对象，我们可以通过下面这个例子来证明这一点
//
//// person 对象
//public class Person {
//    private String name;
//    private int age;
//    private String email;
//    private String desc;
////    ...省略get/set...
// }
//public class PersonApp {
//   public static void main(String[] args) {
//       // 初始化一个对象
//       Person person = new Person("张三",20,"123456@qq.com","我是张三");
//       // 复制对象
//       Person person1 = person;
//       // 改变 person1 的属性值
//       person1.setName("我不是张三了");
//        System.out.println("person对象："+person);
//        System.out.println("person1对象："+person1);
//
//   }
//}
//运行上面代码，你会得到如下结果：
//
//person对象：Person{name='我不是张三了', age=20, email='123456@qq.com', desc='我是张三'}
//person1对象：Person{name='我不是张三了', age=20, email='123456@qq.com', desc='我是张三'}
//我们将 person 对象复制给了 person1 对象，我们对 person1 对象的 name 属性进行了修改，并未修改 person 对象的name 属性值，但是我们最后
//发现 person 对象的 name 属性也发生了变化，其实不止这一个值，对于其他值也是一样的，所以这结果证明了我们上面的结论：直接赋值的方式没有生产新的对
//象，只是生新增了一个对象引用，直接赋值在 Java 内存中的模型大概是这样的
//
//
//《02 浅拷贝》
//浅拷贝也可以实现对象克隆，从这名字你或许可以知道，这种拷贝一定存在某种缺陷，是的，它就是存在一定的缺陷，先来看看浅拷贝的定义：如果原型对象的成员变量是
//值类型，将复制一份给克隆对象，也就是说在堆中拥有独立的空间；如果原型对象的成员变量是引用类型，则将引用对象的地址复制一份给克隆对象，也就是说原型对象和克
//隆对象的成员变量指向相同的内存地址。换句话说，在浅克隆中，当对象被复制时只复制它本身和其中包含的值类型的成员变量，而引用类型的成员对象并没有复制。 可能你
//没太理解这段话，那么我们在来看看浅拷贝的通用模型：
//
//要实现对象浅拷贝还是比较简单的，只需要被复制类需要实现 Cloneable 接口，重写 clone 方法即可，对 person 类进行改造，使其可以支持浅拷贝。
//
//class Person implements Cloneable {
//    private String name;
//    private int age;
//    private String email;
//    private String desc;
//    public Person(String name,int age,String email,String desc) {
//    	this.name=name;
//    	this.age=age;
//    	this.email=email;
//    	this.desc=desc;
//    }
//	public String getName() {
//		return name;
//	}
//    public void setName(String name) {
//		this.name=name;
//	}
//    public int getAge() {
//		return age;
//	}
//	public void setAge(int age) {
//		this.age = age;
//	}
//    public String getEmail() {
//		return email;
//	}
//	public void setEmail(String email) {
//		this.email = email;
//	}
//    public String getDesc() {
//		return desc;
//	}
//	public void setDesc(String desc) {
//		this.desc = desc;
//	}
//	public String toString() {
//		return " 姓名："+this.name+" 年龄："+this.age+" Email:"+this.email+" 说明："+this.desc;
//	}
//    /*
//    * 重写 clone 方法，需要将权限改成 public ，直接调用父类(此处父类为Object)的 clone 方法就好了
//    */
//    @Override
//    public Object clone() throws CloneNotSupportedException {
//        return super.clone();
//    }
////    ...省略...
//}
////改造很简单只需要让 person 继承 Cloneable 接口，并且重写 clone 方法即可，clone 也非常简单只需要调用 object 的 clone 方法就好，唯一需要注意的
////地方就是 clone 方法需要用 public 来修饰，在简单的修改 main 方法
////
//public class cloneable接口 {
//    public static void main(String[] args) throws Exception {
//        // 初始化一个对象
//        Person person = new Person("张三",20,"123456@qq.com","我是张三");
//        // 复制对象
//        Person person1 = (Person) person.clone();
//        // 改变 person1 的属性值
//        person1.setName("我是张三的克隆对象");
//        // 修改 person age 的值
//        person1.setAge(22);
//        System.out.println("person对象："+person);
//        System.out.println();
//        System.out.println("person1对象："+person1);
//
//    }
//}
//重新运行 main 方法，结果如下：
//
//person对象：Person{name='张三', age=20, email='123456@qq.com', desc='我是张三'}
//person1对象：Person{name='我是张三的克隆对象', age=22, email='123456@qq.com', desc='我是张三'}
//看到这个结果，你是否有所质疑呢？说好的引用对象只是拷贝了地址，为啥修改了 person1 对象的 name 属性值，person 对象没有改变？这里就是一个非常重要
//的知识点了，原因在于：String、Integer 等包装类都是不可变的对象，当需要修改不可变对象的值时，需要在内存中生成一个新的对象来存放新的值，然后将原来
//的引用指向新的地址，所以在这里我们修改了 person1 对象的 name 属性值，person1 对象的 name 字段指向了内存中新的 name 对象，但是我们并没有改
//变 person 对象的 name 字段的指向，所以 person 对象的 name 还是指向内存中原来的 name 地址，也就没有变化
//
//这种引用是一种特列，因为这些引用具有不可变性，并不具备通用性，所以我们就自定义一个类，来演示浅拷贝，我们定义一个 PersonDesc 类用来存放person 对象
//中的 desc 字段，然后在 person 对象中引用 PersonDesc 类，具体代码如下：
//
//// 新增 PersonDesc 
//public class PersonDesc {
//    // 描述
//    private String desc;
//
//}
//public class Person implements Cloneable {
//    // 姓名
//    private String name;
//    // 年龄
//    private int age;
//    // 邮件
//    private String email;
//    // 将原来的 string desc 变成了 PersonDesc 对象，这样 personDesc 就是引用类型
//    private PersonDesc personDesc;
//
//    @Override
//    public Object clone() throws CloneNotSupportedException {
//        return super.clone();
//    }
//    public void setDesc(String desc) {
//        this.personDesc.setDesc(desc);
//    }
//    public Person(String name, int age, String email, String desc) {
//        this.name = name;
//        this.age = age;
//        this.email = email;
//        this.personDesc = new PersonDesc();
//        this.personDesc.setDesc(desc);
//    }
////     ...省略...
//}
//修改 main 方法
//
//public class PersonApp {
//    public static void main(String[] args) throws Exception {
//        // 初始化一个对象
//        Person person = new Person("平头",20,"123456@qq.com","技术");
//        // 复制对象
//        Person person1 = (Person) person.clone();
//        // 改变 person1 的属性值
//        person1.setName("我是平头的克隆对象");
//        // 修改 person age 的值
//        person1.setAge(22);
//        person1.setDesc("我已经关注了技术");
//        System.out.println("person对象："+person);
//        System.out.println();
//        System.out.println("person1对象："+person1);
//    }
//}
//运行 main 方法，得到如下结果：
//
//person对象：Person{name='平头', age=20, email='123456@qq.com', desc='我已经关注了技术'}
//
//person1对象：Person{name='我是平头的克隆对象', age=22, email='123456@qq.com', desc='我已经关注了技术'}
//我们修改 person1 的 desc 字段之后，person 的 desc 也发生了改变，这说明 person 对象和 person1 对象指向是同一个 PersonDesc 对象地址，
//这也符合浅拷贝引用对象只拷贝引用地址并未创建新对象的定义，到这你应该知道浅拷贝了吧。
//
//《03 深拷贝》
//深拷贝也是对象克隆的一种方式，相对于浅拷贝，深拷贝是一种完全拷贝，无论是值类型还是引用类型都会完完全全的拷贝一份，在内存中生成一个新的对象，简单点说就
//是拷贝对象和被拷贝对象没有任何关系，互不影响。深拷贝的通用模型如下：
//
//深拷贝有两种方式，一种是跟浅拷贝一样实现 Cloneable 接口，另一种是实现 Serializable 接口，用序列化的方式来实现深拷贝，我们分别用这两种方式来实现深拷贝
//
//1.实现 Cloneable 接口方式
//实现 Cloneable 接口的方式跟浅拷贝相差不大，我们需要引用对象也实现 Cloneable 接口，具体代码改造如下：
//
//public class PersonDesc implements Cloneable{
//
//    // 描述
//    private String desc;
//    ...省略...
//    @Override
//    public Object clone() throws CloneNotSupportedException {
//        return super.clone();
//    }
//}
//public class Person implements Cloneable {
//    // 姓名
//    private String name;
//    // 年龄
//    private int age;
//    // 邮件
//    private String email;
//
//    private PersonDesc personDesc;
//
//    /**
//    * clone 方法不是简单的调用super的clone 就好，
//    */
//    @Override
//    public Object clone() throws CloneNotSupportedException {
//        Person person = (Person)super.clone();
//        // 需要将引用对象也克隆一次
//        person.personDesc = (PersonDesc) personDesc.clone();
//        return person;
//    }
//    ...省略...
//}
//main 方法不需要任何改动，我们再次运行 main 方法，得到如下结果：
//
//person对象：Person{name='平头', age=20, email='123456@qq.com', desc='技术'}
//
//person1对象：Person{name='平头的克隆对象', age=22, email='123456@qq.com', desc='我已经关注了技术'}
//可以看出，修改 person1 的 desc 时对 person 的 desc 已经没有影响了，说明进行了深拷贝，在内存中重新生成了一个新的对象。
//
//2.实现 Serializable 接口方式
//实现 Serializable 接口方式也可以实现深拷贝，而且这种方式还可以解决多层克隆的问题，多层克隆就是引用类型里面又有引用类型，
//层层嵌套下去，用 Cloneable 方式实现还是比较麻烦的，一不小心写错了就不能实现深拷贝了，使用 Serializable 序列化的方式就需要所有的对
//象对实现 Serializable 接口，我们对代码进行改造，改造成序列化的方式
//
//public class Person implements Serializable {
//
//    private static final long serialVersionUID = 369285298572941L;
//    // 姓名
//    private String name;
//    // 年龄
//    private int age;
//    // 邮件
//    private String email;
//
//    private PersonDesc personDesc;
//
//    public Person clone() {
//        Person person = null;
//        try { // 将该对象序列化成流,因为写在流里的是对象的一个拷贝，而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝
//            ByteArrayOutputStream baos = new ByteArrayOutputStream();
//            ObjectOutputStream oos = new ObjectOutputStream(baos);
//            oos.writeObject(this);
//            // 将流序列化成对象
//            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
//            ObjectInputStream ois = new ObjectInputStream(bais);
//            person = (Person) ois.readObject();
//        } catch (IOException e) {
//            e.printStackTrace();
//        } catch (ClassNotFoundException e) {
//            e.printStackTrace();
//        }
//        return person;
//    }
//
//    public void setDesc(String desc) {
//        this.personDesc.setDesc(desc);
//    }
//  ...省略...
//}
//public class PersonDesc implements Serializable {
//
//    private static final long serialVersionUID = 872390113109L; 
//    // 描述
//    private String desc;
//
//    public String getDesc() {
//        return desc;
//    }
//
//    public void setDesc(String desc) {
//        this.desc = desc;
//    }
//
//}
//public class PersonApp {
//    public static void main(String[] args) throws Exception {
//        // 初始化一个对象
//        Person person = new Person("平头",20,"123456@qq.com","技术");
//        // 复制对象
//        Person person1 = (Person) person.clone();
//        // 改变 person1 的属性值
//        person1.setName("我是平头的克隆对象");
//        // 修改 person age 的值
//        person1.setAge(22);
//        person1.setDesc("我已经关注了技术");
//        System.out.println("person对象："+person);
//        System.out.println();
//        System.out.println("person1对象："+person1);
//    }
//}
//运行 main 方法，我们可以得到跟 Cloneable 方式一样的结果，序列化的方式也实现了深拷贝。到此关于 Java 浅拷贝和深拷贝的相关内容就介绍完了，希望你有所收获。


